package com.googlecode.uipipedream.impl;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;

import com.googlecode.uipipedream.spec.ColourModel;
import com.googlecode.uipipedream.spec.HSBType;
import com.googlecode.uipipedream.spec.SliderBar;

/**
 * <p>
 * This file is part of UIPipeDream.
 * </p>
 * 
 * <p>
 * You can redistribute UIPipeDream and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 * </p>
 * 
 * <p>
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <a
 * href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
 * </p>
 * 
 * @author <a href="http://daemonsadvocate.blogspot.com/">Matthew B. Gray</a>
 * @author James Jones
 * @version 1.0 Final
 */
public class HorizontalSliderBar extends SliderBar {
	private static final long serialVersionUID = 4988289504165404166L;
	protected ColourModel model;
	private HSBType type;

	// Drawable items that overlay the rainbow-esk bar.
	protected Rectangle rainbowBar;
	protected Dimension stdOutrigger;
	private Dimension selector;
	protected Rectangle[] nSelectors;
	private Polygon[] triangles = new Polygon[2];

	// Boolean state tracker
	private boolean draggingBox = false;

	// Internal trackers
	private Dimension cachedCanvasSize;
	private Image drawing;
	protected Graphics graphics = null;
	private boolean movingOutrigger;
	private float startedWithHue;

	public HorizontalSliderBar(ColourModel model, HSBType type) {
		super(model);
		this.model = model;
		this.type = type;
	}

	@Override
	public Dimension preferredSize() {
		return new Dimension(500, 10);
	}

	@Override
	public Dimension minimumSize() {
		return new Dimension(500, 10);
	}

	public synchronized void paint(Graphics g) {
		// 0. Cache dimensions and setup object.
		Dimension d = getSize();
		if ((graphics == null) || (d.width != cachedCanvasSize.width)
				|| (d.height != cachedCanvasSize.height)) {
			refreshCachedValues(d);
		}
		refreshDrawableItems(d);

		clearCanvas(d);
		paintRainbowBar();

		if (!draggingBox)
			paintColourSelectors();
		else
			paintColourIndicators();

		g.drawImage(drawing, 0, 0, this);
	}

	protected void refreshDrawableItems(Dimension d) {
		// 1. selectors are what makes up the colour selection.
		this.nSelectors = new Rectangle[model.getNColours()];
		Point leftCorner, rightCorner;
		float incrementalOffset;
		final int centerOfBar = rainbowBar.x + rainbowBar.width / 2;
		final int halfNSelectors = nSelectors.length / 2;
		for (int i = halfNSelectors; 0 < i; i--) {
			// 1.1. Setup incremental offset as a size
			incrementalOffset = i * model.getOffset() * rainbowBar.width;

			// 1.2. Create corner points from center value.
			leftCorner = new Point(
					(int) (centerOfBar - incrementalOffset - stdOutrigger.width / 2),
					(int) (0.02 * d.height));
			rightCorner = new Point(
					(int) (centerOfBar + incrementalOffset - stdOutrigger.width / 2),
					(int) (0.02 * d.height));

			// 1.3. Assign to selectors
			nSelectors[halfNSelectors - i] = new Rectangle(leftCorner,
					stdOutrigger);
			nSelectors[halfNSelectors + i] = new Rectangle(rightCorner,
					stdOutrigger);
		}

		// 2. setup middle bar
		Point corner = new Point((int) (centerOfBar - selector.width / 2),
				(int) (0.02 * d.height));
		nSelectors[halfNSelectors] = new Rectangle(corner, selector);

		// 3. setup the triangles you're going to see when you drag on the
		// middle bar
		final int triangleHeights = (int) (0.20 * selector.height);
		final int triangleTop = corner.y;
		final int triangleBottom = corner.y + selector.height;

		triangles[0] = new Polygon();
		triangles[0].addPoint(corner.x, triangleTop);
		triangles[0].addPoint(corner.x + selector.width, triangleTop);
		triangles[0].addPoint(corner.x + selector.width / 2, triangleTop
				+ triangleHeights);

		triangles[1] = new Polygon();
		triangles[1].addPoint(corner.x, triangleBottom);
		triangles[1].addPoint(corner.x + selector.width, triangleBottom);
		triangles[1].addPoint(corner.x + selector.width / 2, triangleBottom
				- triangleHeights);
	}

	protected void paintColourIndicators() {
		graphics.setColor(model.getColor());
		graphics.fillPolygon(triangles[0]);
		graphics.fillPolygon(triangles[1]);

		graphics.setColor(Color.white);
		graphics.drawPolygon(triangles[0]);
		graphics.drawPolygon(triangles[1]);
	}

	protected void paintColourSelectors() {
		final float[] hues = model.getAnalogusHues();
		Color currColour;
		for (int i = 0; i < nSelectors.length; i++) {
			currColour = Color.getHSBColor(hues[i], model.getSaturation(),
					model.getBrightness());

			graphics.setColor(currColour);
			graphics.fillRect(nSelectors[i].x, nSelectors[i].y,
					nSelectors[i].width, nSelectors[i].height);

			graphics.setColor(Color.white);
			graphics.drawRect(nSelectors[i].x, nSelectors[i].y,
					nSelectors[i].width, nSelectors[i].height);
		}
	}

	protected void clearCanvas(Dimension d) {
		graphics.setColor(getBackground());
		graphics.fillRect(0, 0, d.width, d.height);
		graphics.setColor(Color.black);
	}

	protected void paintRainbowBar() {
		for (int i = 1; i < rainbowBar.width; i++) {
			float hue = getHue(i) + model.getHue();
			graphics.setColor(Color.getHSBColor(hue, model.getSaturation(),
					model.getBrightness()));
			final int x = rainbowBar.x + i;
			this.graphics.drawLine(x, rainbowBar.y, // Point 1
					x, rainbowBar.y + rainbowBar.height); // Point 2
		}
		graphics.setColor(Color.white);
		graphics.drawRect(rainbowBar.x, rainbowBar.y, rainbowBar.width,
				rainbowBar.height);
	}

	/**
	 * These are values that are drawn against. This method updates local
	 * variables based on the size of the current canvas.
	 */
	protected void refreshCachedValues(Dimension d) {
		this.cachedCanvasSize = d;
		this.drawing = createImage(d.width, d.height);
		this.graphics = this.drawing.getGraphics();

		// 1. draw the bounding box 5% in from all edges.
		this.rainbowBar = new Rectangle(
				// X, Y, Width, Height
				(int) (0.05 * d.width), (int) (0.05 * d.height),
				(int) (0.90 * d.width), (int) (0.90 * d.height));

		// 2. Setup the selector
		this.selector = new Dimension((int) (0.05 * d.width),
				(int) (0.96 * d.height));

		// 3. outriggers are 1/2 the size of a selector
		this.stdOutrigger = new Dimension((int) (0.5 * selector.width),
				selector.height);

	}

	@Override
	public boolean mouseDrag(Event e, int x, int y) {
		x -= rainbowBar.x;

		if (draggingBox) {
			model.setHue(getHue(x) + startedWithHue);
			return true;
		} else if (movingOutrigger) {
			int centerBar = rainbowBar.width / 2;
			float offset = (float) Math.abs(x - centerBar) / rainbowBar.width;
			model.setOffset(offset);
			return true;
		}
		return false;
	}

	/**
	 * Gets the hue relative to some X co-ordinate
	 * 
	 * @param x
	 *            A value from the side of the component to the point where the
	 *            mouse is.
	 * @return
	 */
	protected float getHue(int x) {
		float toReturn = ((float) x / rainbowBar.width + 0.5f) % 1f;
		return toReturn;
	}

	/**
	 * The only way to interact with this bar is by sliding the rectangles that
	 * sit on top of it left and right.
	 */
	@Override
	public boolean mouseDown(Event e, int x, int y) {
		draggingBox = false;

		// For each of the selectors
		for (int i = 0; i < nSelectors.length; i++) {
			Rectangle bar = nSelectors[i];

			// 1. If any contain the co-ordinates x and y
			if (bar.contains(x, y)) {
				if (bar.getSize().equals(selector)) {
					// 1.1 And it's the central box (i.e., main colour selector)
					this.draggingBox = true;
					this.startedWithHue = model.getHue();
					// 1.2. Put in those triangles!
					this.repaint();
					return true;
				} else {
					this.movingOutrigger = true;
				}
			}
		}

		// if (rainbowBar.contains(x, y)) {
		// draggingBox = true;
		// this.mouseEnteredAt = x - rainbowBar.x;
		// this.repaint();
		// return true;
		// }
		//		
		// float curr = 0;
		// switch (type) {
		// case HUE:
		// curr = model.getHue();
		// break;
		// case SATURATION:
		// curr = model.getSaturation();
		// break;
		// case BRIGHTNESS:
		// curr = model.getBrightness();
		// break;
		// }
		//		
		// int dx = x - rainbowBar.x - (int) (rainbowBar.width * (1f - curr));
		// if (y < rainbowBar.y && y >= rainbowBar.y - 8 && dx >= -4 && dx <= 4)
		// {
		// draggingBox = true;
		// return true;
		// }
		return false;
	}

	@Override
	public boolean mouseUp(Event e, int x, int y) {
		this.draggingBox = false;
		this.repaint(); // Take away triangles.
		return false;
	}

	private static final Cursor CURSOR_HAND = new Cursor(Cursor.W_RESIZE_CURSOR);
	private static final Cursor CURSOR_NORMAL = new Cursor(
			Cursor.DEFAULT_CURSOR);

	@SuppressWarnings("deprecation")
	@Override
	public boolean mouseMove(Event evt, int x, int y) {
		boolean hoveringOverBox = false;
		if (nSelectors != null)
			for (Rectangle box : this.nSelectors) {
				hoveringOverBox = box.contains(x, y);
				if (hoveringOverBox) {
					setCursor(CURSOR_HAND);
					break;
				} else {
					setCursor(CURSOR_NORMAL);
				}
			}
		return super.mouseMove(evt, x, y);
	}
}
